package cn.com.idmy.auth.token;

import cn.com.idmy.auth.AuthConfig;
import cn.com.idmy.auth.context.Contexts;
import cn.com.idmy.auth.cookie.CookieManager;
import cn.com.idmy.auth.login.LoginState;
import cn.com.idmy.base.util.Assert;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.hutool.core.data.id.IdUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.function.Consumer;

import static cn.com.idmy.auth.AuthConfig.ACCOUNT_TYPE_KEY;

@Data
@Accessors(fluent = true)
public abstract class TokenManager<ID> {
    public static final String ORIGINAL_TOKEN_STORAGE_KEY_NOT_PREFIX = "token:storage:original:not:prefix";
    public static final String ORIGINAL_TOKEN_STORAGE_KEY = "token:storage";
    public static final String LOGIN_STATE_STORAGE_KEY = "login:state:storage";
    @NotNull
    protected volatile Consumer<LoginState<ID>> tokenCreator = (state) -> IdUtil.simpleUUID();
    @NotNull
    protected volatile CookieManager cookieManager = new CookieManager();
    protected volatile TokenConfigDao<ID> tokenConfigDao;

    public synchronized void init() {
        Assert.notNull(cookieManager, "cookieManager 不能为空");
        Assert.notNull(tokenCreator, "tokenCreator 不能为空");
        Assert.notNull(tokenConfigDao, "tokenConfigDao 不能为空");
    }

    public void create(@NotNull LoginState<ID> state) {
        tokenCreator.accept(state);
    }

    @Nullable
    public String get(@NotNull LoginState<ID> state) {
        return state.token();
    }

    public void set(@NotNull LoginState<ID> state) {
        var token = Assert.notBlank(state.token(), "token 不能为空");
        setStorage(state);
        var name = AuthConfig.tokenCookieName();
        if (StrUtil.isNotBlank(name)) {
            cookieManager.addCookie(state, name, token);
        }
    }

    protected void setStorage(@NotNull LoginState<ID> state) {
        var token = state.token();
        var storage = Contexts.storage();
        var prefix = AuthConfig.tokenHeaderPrefix();
        if (StrUtil.isBlank(prefix)) {
            storage.set(ORIGINAL_TOKEN_STORAGE_KEY, token);
        } else {
            storage.set(ORIGINAL_TOKEN_STORAGE_KEY, prefix + token);
        }
        storage.set(ORIGINAL_TOKEN_STORAGE_KEY_NOT_PREFIX, token);
        storage.set(ACCOUNT_TYPE_KEY, state.accountType());
    }

    public @Nullable String token() {
        var token = originToken();
        if (StrUtil.isBlank(token)) {
            return null;
        }
        var prefix = AuthConfig.tokenHeaderPrefix();
        if (StrUtil.isBlank(prefix)) {
            return token;
        } else if (token.startsWith(prefix)) {
            return token.substring(prefix.length());
        } else {
            return token;
        }
    }

    protected @Nullable String originToken() {
        var storage = Contexts.storage();
        String token = null;
        if (storage.has(ORIGINAL_TOKEN_STORAGE_KEY)) {
            token = (String) storage.get(ORIGINAL_TOKEN_STORAGE_KEY);
        }
        var request = Contexts.request();
        if (token == null) {
            var name = AuthConfig.tokenCookieName();
            if (StrUtil.isNotBlank(name)) {
                token = request.getCookieValue(name);
            }
        }
        if (token == null) {
            var name = AuthConfig.tokenHeaderName();
            if (StrUtil.isNotBlank(name)) {
                token = request.getHeader(name);
            }
        }
        if (token == null) {
            var name = AuthConfig.tokenBodyName();
            if (StrUtil.isNotBlank(name)) {
                token = request.getNullableParam(name);
            }
        }
        return token;
    }

    public @Nullable LoginState<ID> state() {
        var storage = Contexts.storage();
        if (storage.has(LOGIN_STATE_STORAGE_KEY)) {
            return Assert.notNull(storage.get(LOGIN_STATE_STORAGE_KEY, LoginState.class));
        } else {
            var token = token();
            if (StrUtil.isBlank(token)) {
                return null;
            } else {
                var state = getState(token);
                storage.set(LOGIN_STATE_STORAGE_KEY, state);
                return state;
            }
        }
    }

    protected abstract @NotNull LoginState<ID> getState(String token);

    public @Nullable ID id() {
        var state = state();
        return state == null ? null : state.id();
    }

    @NotNull
    public JSONObject data() {
        var model = state();
        return model == null ? JSONObject.of() : model.data();
    }
}
